﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ParserCore;
using NDimArrayNamespace;
using AtomicTypes;
using Memory_Access;
using QLabValues;
using System.Diagnostics;

namespace Engine
{
    class EngineVisitor:IVisitor
    {
        private List<IQLabValue> tempAns;

        private bool continueFlag, breakFlag;
        // flagovi za kontrolu toka

        private bool isNestedFunc;
        // flag za kontrolu poziva ugnjezdene funkcije

        private VarTable variableTable;
        // tabela simbola

        public VarTable VariableTable
        {
            get
            {
                return variableTable;
            }
            set
            {
                variableTable = value;
            }
        }

        private FunctionTable nestedFuncTable, subFuncTable, privateFuncTable;
        // tabele funkcija

        public FunctionTable NestedFuncTable
        {
            get
            {
                return nestedFuncTable;
            }
            set
            {
                nestedFuncTable = value;
            }
        }

        public FunctionTable SubFuncTable
        {
            get
            {
                return subFuncTable;
            }
            set
            {
                subFuncTable = value;
            }
        }

        public FunctionTable PrivateFuncTable
        {
            get
            {
                return privateFuncTable;
            }
            set
            {
                privateFuncTable = value;
            }
        }
                
        public EngineVisitor()
        {
            /* pocetne inicijalizacije trenutnih rezultata+pocetne inicijalizacije tabela */
            tempAns = new List<IQLabValue>();
            continueFlag = false;
            breakFlag = false;
            variableTable = new VarTable();
            nestedFuncTable = new FunctionTable();
            subFuncTable = new FunctionTable();
            privateFuncTable = new FunctionTable();
        }
       
        /// <summary>
        /// Fills tables of nested and/or subfunctions of a function.
        /// </summary>
        /// <param name="stmt">Either a FunctionDefinitionStatement (no subfunctions), or a BlockStatement consisting of multiple
        /// FunctionDefinitionStatements (where the first FDS is the main function and the rest are its subfunctions).</param>
        public void FillFunctionTables(Statement stmt)
        {
            if (stmt is FunctionDefinitionStatement) // no subfunctions
            {
                AddNestedFunctions((FunctionDefinitionStatement)stmt);
            }
            else if (stmt is BlockStatement) // the first statement is the main function, the rest are subfunctions
            {
                BlockStatement functionBlockStmt = (BlockStatement)stmt;
                AddSubFunctions(functionBlockStmt);

                Debug.Assert(functionBlockStmt.StatementList[0] is FunctionDefinitionStatement);
                AddNestedFunctions((FunctionDefinitionStatement)functionBlockStmt.StatementList[0]); // TODO: what about subfunctions?
            }
            else // the argument is not valid; error
            {
                throw new ArgumentException();
            }
        }

        private void AddSubFunctions(BlockStatement function)
        // upisuje podfunkcije u tabelu podfunkcija
        {
                Debug.Assert(function.StatementList[0] is FunctionDefinitionStatement);
                FunctionDefinitionStatement mainFunction = (FunctionDefinitionStatement)(function.StatementList[0]);
                FunctionDefinitionStatement subfunction;

                for (int i=0; i<function.StatementList.Length-1; i++)
                {
                    Debug.Assert(function.StatementList[i+1] is FunctionDefinitionStatement);
                    subfunction = (FunctionDefinitionStatement)function.StatementList[i+1];
                    subFuncTable.AddParsedFunction(subfunction);
                }
        }
 
        private void AddNestedFunctions(FunctionDefinitionStatement function)
        // upisuje ugnjezdene funkcije u tabelu ugnjezdenih funkcija
        // does NOT go deeper than one level (does not insert nested functions of nested functions of the argument)
        {
 	        bool insideNestedFunction = false;
            if  (!(function.FunctionBody is BlockStatement)) // no nested functions inside a single statement
                return;

            BlockStatement bodyBlock = (BlockStatement)(function.FunctionBody);
            foreach (Statement statement in bodyBlock.StatementList)
            // nested functions cannot be defined inside loops etc, so we do not have to perform a deeper check
            {
                if (!insideNestedFunction && (statement is FunctionDefinitionStatement))
                {
                    nestedFuncTable.AddParsedFunction((FunctionDefinitionStatement)statement);
                }
            }
        }

        private int[][] GetSubIndex(Expression[] argumentList)
        {
            int[][] subCoordinates=null;
            argumentList[0].Accept(this);
            if (tempAns[0].GetType() != typeof(QInt64Array))
                throw new EngineException("0. argument has invalid index type");
            subCoordinates[1] = ((QInt64Array)tempAns[0]).ToIntArray();
            if (tempAns[0].GetType() != typeof(QInt64Array))
                throw new EngineException("1. argument has invalid index type");
            subCoordinates[0] = ((QInt64Array)tempAns[0]).ToIntArray();
            for (int i = 2; i < argumentList.Length; i++)
            {
                argumentList[i].Accept(this);
                if (tempAns[0].GetType() != typeof(QInt64Array))
                    throw new EngineException(i + ". argument has invalid index type");
                subCoordinates[i] = ((QInt64Array)tempAns[0]).ToIntArray();
            }
            return subCoordinates;                                                                                         
        }

        private bool IsArray(IQLabValue variable)
        {
            return (variable.GetType() == typeof(QInt64Array)) || (variable.GetType() == typeof(QUInt64Array)) || (variable.GetType() == typeof(QFloat64Array)) || (variable.GetType() == typeof(QCharArray)) || (variable.GetType() == typeof(QBooleanArray)) || (variable.GetType() == typeof(StructMatrix)) || (variable.GetType() == typeof(CellValue));
        }
        FunctionImplementation LookUpAllTables(Identifier ID)
        {
            return null;
        }


        /// <summary>
        /// Fills the variable table of the visitor with all values from <em>table</em> whose keys are not found in <em>returnList</em>.
        /// </summary>
        /// <param name="table"></param>
        /// <param name="returnList"></param>
        private void UpdateVariableTable(VarTable table, Identifier[] returnList)
        {
            Dictionary <Identifier, IQLabValue> dictionary = table.Table;
            foreach (Identifier id in dictionary.Keys.AsEnumerable()) 
            {
                Boolean foundId = false;
                foreach (Identifier idreturn in returnList) //TODO: optimize for speed
                {
                    if (idreturn.Equals(id))
                        foundId = true;
                }
                if (!foundId)
                    this.VariableTable.AddVariable(id, table.LookUp(id));
            }
        }
        
        public void Visit(Constant exp)
        // podatak tipa string kastuje u odgovarajuci tip i postavi privremeni rezultat na tu vrednost
        {
            tempAns.Clear();
            switch (exp.ConstType)
            {
                case Constant.CType.RealNumber:
                    tempAns.Add(new QFloat64Array(new QFloat64[1]{ new QFloat64(Convert.ToDouble(exp.Const)) }, new int[2]{ 1, 1 }));
                    break;
                case Constant.CType.ImaginaryNumber:
                    tempAns.Add(new QFloat64Array(new QFloat64[1]{ new QFloat64(0, Convert.ToDouble(exp.Const)) }, new int[2]{ 1, 1 }));
                    break;
                case Constant.CType.String:
                    QChar[] tempQCharArray=new QChar[exp.Const.Length];
                    for (int i=0; i<exp.Const.Length; i++)
                        tempQCharArray[i]=new QChar(exp.Const[i]);
                    tempAns.Add(new QCharArray(tempQCharArray, new int[2]{ 1, exp.Const.Length }));
                    break;
            }
        }

        public void Visit(Identifier exp)
        {
            tempAns.Clear();
            IQLabValue variable = variableTable.LookUp(exp);
            if (variable != null)
                tempAns.Add(variable);
            else
            {
                // ako ID nije pronadjen u tabeli simbola tretira se kao poziv funkcije bez argumenata
                // HINT: konstante(pi, e...) su pozivi funkcija bez argumenata
                Call tempCall=new Call(exp, new Expression[]{} );
                tempCall.Accept(this);
            }
        }

        public void Visit(Call exp)
        {
            // HINT: ako definisemo funkciju koja se zove kao BuiltIn funkcija, matlab izbacuje warning i pri svakom pozivu te funkcije poziva se BuiltIn funkcija, s druge strane moguce je definisati promenljivu da se zove kao BuiltIn funkcija i promenljiva ima prioritet.
            tempAns.Clear();
            ParsingEngine tempParser = new ParsingEngine();
            EngineVisitor tempVisitor, tempArgVisitor;
            FunctionImplementation calledFunction;

            int[][] subCoordinates;

            // QUESTION: Zasto je exp.Called Expression, a ne Identifier, ISPAVRKA s.f=@(x)x+1  s.f(5) je  call(call(.,s,f),5) ?
            IQLabValue calledVariable;
            if (exp.Called is Identifier)
            //TODO: ako nije Identifier, izvrsi exp.Called expression, dodaj ga u tabelu simbola kao vrednost promenljive __x(nedozovoljen naziv promenljive!) i napravimo tempCall=new call(new Identifier("__X"),exp.Arglist); tempCall.Accept(this);
            {
                calledVariable = variableTable.LookUp((Identifier)exp.Called);
                if (calledVariable != null)
                // ako je call promenljiva, imamo dva scenarija:
                {
                    if (IsArray(calledVariable))
                        // 1. called variable je identifier matrice, pa je call vadjenje podmatrice
                    {
                        if (exp.ArgumentList.Length == 0)
                            tempAns.Add(calledVariable);
                        else
                            if (exp.ArgumentList.Length == 1)
                            {
                                // TODO: ako operator vadjenja podmatrice ima jedan argument, cela matrica se gleda kao niz (npr a11,a21,a31,a12,a22,a32,a13,a23,a33)
                            }
                            else
                            {
                                subCoordinates = GetSubIndex(exp.ArgumentList);
                                switch (calledVariable.getEnumType())
                                {
                                    case IQLabValueType.QBooleanArray:
                                        tempAns.Add(((QBooleanArray)calledVariable).GetSubArray(subCoordinates));
                                        break;
                                    case IQLabValueType.QCharArray:
                                        tempAns.Add(((QCharArray)calledVariable).GetSubArray(subCoordinates));
                                        break;
                                    case IQLabValueType.QInt64Array:
                                        tempAns.Add(((QInt64Array)calledVariable).GetSubArray(subCoordinates));
                                        break;
                                    case IQLabValueType.QFloat64Array:
                                        tempAns.Add(((QFloat64Array)calledVariable).GetSubArray(subCoordinates));
                                        break;
                                    case IQLabValueType.StructMatrix:
                                        tempAns.Add((StructMatrix)((StructMatrix)calledVariable).GetSubNDimArray(subCoordinates));
                                        break;
                                    case IQLabValueType.CellValue:
                                        tempAns.Add((CellValue)((CellValue)calledVariable).GetSubNDimArray(subCoordinates));
                                        break;
                                }
                                return;
                            }
                    }
                    else
                        // 2. called variable je FunctionHandle, pravimo call sa IDem handlea i pozivamo visit tog novog calla
                        if (calledVariable is FunctionHandle)
                        {

                            //Call tempCall = new Call();
                            calledFunction = nestedFuncTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                            // ugnjezdene funkcije mogu da pozivaju druge ugnjezdene funkcije, ali samo one mogu da pozivaju svoje ugnjezdene funkcije!
                            if (calledFunction != null)
                            {
                                if (calledFunction is MatlabFunction)
                                {
                                    tempVisitor = new EngineVisitor();
                                    tempVisitor.variableTable = variableTable;
                                    tempVisitor.subFuncTable = subFuncTable;
                                    // druge ugnjezdene funkcije mozemo da posmatramo kao subfunctions
                                    tempVisitor.subFuncTable.AddFunctions(nestedFuncTable);
                                    tempVisitor.privateFuncTable = privateFuncTable;
                                    Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();
                                    // nested function uvek mora da bude definisana kao FuncDefStat jer ne moze da ima svoje podfunkcije, pa ne moze da bude BlockStat
                                    if (function is FunctionDefinitionStatement)
                                    {
                                        // dodaj ugnjezdene funkcije u tabelu ugnjezdenih funkcija, ubaci promenljive iz return liste u tabelu promenljivih (inicijalnog su null)
                                        ((FunctionDefinitionStatement)function).Accept(tempVisitor);
                                        // ubaci argumente funkcije u tabelu promenljivih
                                        for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                                        {
                                            exp.ArgumentList[i].Accept(tempVisitor);
                                            tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], tempVisitor.tempAns[0]);
                                        }
                                        ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                                        tempAns = tempVisitor.tempAns;
                                        UpdateVariableTable(tempVisitor.variableTable, ((FunctionDefinitionStatement)function).ReturnList);
                                    }
                                    else
                                    {
                                        //greska!
                                    }
                                    return;
                                }
                                if (calledFunction is BuiltInFunction)
                                {
                                    // TODO: poziv ugradjene funkcije
                                    return;
                                }
                            }
                            calledFunction = subFuncTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                            if (calledFunction == null)
                            {
                                calledFunction = privateFuncTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                                if (calledFunction == null)
                                {
                                    calledFunction = EngineManager.Instance.currentFolderTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                                    if (calledFunction == null)
                                    {
                                        calledFunction = EngineManager.Instance.pathTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                                        if (calledFunction == null)
                                        {
                                            //throw new NotFoundVariableException("{0} not found", calledFunction.FcnName);
                                            return;
                                        }
                                    }
                                }
                            }
                            if (calledFunction is MatlabFunction)
                            {
                                tempVisitor = new EngineVisitor();
                                Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();
                                //tempVisitor.AddNestedFunctions(function);
                                if (function is BlockStatement)
                                {
                                    tempVisitor.AddSubFunctions((BlockStatement)function);
                                    // prvi Statement u Block Statementu funkcije mora da bude FunctionDefinitionStatment
                                    function = ((BlockStatement)function).StatementList[0];
                                }
                                for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                                {
                                    tempArgVisitor = new EngineVisitor();
                                    exp.ArgumentList[i].Accept(tempArgVisitor);
                                    tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], tempArgVisitor.tempAns[0]);
                                }
                                for (int i = 0; i < ((FunctionDefinitionStatement)function).ReturnList.Length; i++)
                                {
                                    tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ReturnList[i], null);
                                }
                                ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                                tempAns = tempVisitor.tempAns;
                                variableTable = tempVisitor.variableTable;
                                return;
                            }
                            if (calledFunction is BuiltInFunction)
                            {
                                return;
                            }

                        }
                                else
                                    // 3. Anonimna Funkcija
                                    if (calledVariable is AnonymousFunction)
                                    {
                                        //TODO: call AnonymousFunction
                                        return;
                                    }
                }
                // poziv funkcije
                calledFunction = nestedFuncTable.LookUp(exp.Called);
                if (calledFunction != null)
                {
                    if (calledFunction is MatlabFunction)
                    {
                        tempVisitor = new EngineVisitor();
                        tempVisitor.variableTable = variableTable;
                        tempVisitor.subFuncTable = subFuncTable;
                        tempVisitor.subFuncTable.AddFunctions(nestedFuncTable);
                        tempVisitor.privateFuncTable = privateFuncTable;
                        Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();
                        //tempVisitor.AddNestedFunctions(function);
                        for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                        {
                            tempArgVisitor = new EngineVisitor();
                            exp.ArgumentList[i].Accept(tempArgVisitor);
                            tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], tempArgVisitor.tempAns[0]);
                        }
                        for (int i = 0; i < ((FunctionDefinitionStatement)function).ReturnList.Length; i++)
                        {
                            tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ReturnList[i], null);
                        }
                        ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                        tempAns = tempVisitor.tempAns;
                        // TODO: NE TREBA OVAKO, VEC SE DODELE SVE VREDNOSTI KOJE NISU U RETURN ILI ARGUMENTS LISTI!!!
                        variableTable = tempVisitor.variableTable;
                        return;
                    }
                    if (calledFunction is BuiltInFunction)
                    {
                        // TODO: poziv ugradjene funkcije
                        return;
                    }
                }
                calledFunction = subFuncTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                if (calledFunction == null)
                {
                    calledFunction = privateFuncTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                    if (calledFunction == null)
                    {
                        calledFunction = EngineManager.Instance.currentFolderTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                        if (calledFunction == null)
                        {
                            calledFunction = EngineManager.Instance.pathTable.LookUp(((FunctionHandle)calledVariable).GetFunctionHandle());
                            if (calledFunction == null)
                            {
                                //throw new NotFoundVariableException("{0} not found", calledFunction.FcnName);
                                return;
                            }
                        }
                    }
                }
                if (calledFunction is MatlabFunction)
                {
                    tempVisitor = new EngineVisitor();
                    Statement function = ((MatlabFunction)calledFunction).GetFunctionStatement();
                    //tempVisitor.AddNestedFunctions(function);
                    if (function is BlockStatement)
                    {
                        tempVisitor.AddSubFunctions((BlockStatement)function);
                        // prvi Statement u Block Statementu funkcije mora da bude FunctionDefinitionStatment
                        function = ((BlockStatement)function).StatementList[0];
                    }
                    for (int i = 0; i < ((FunctionDefinitionStatement)function).ParameterList.Length; i++)
                    {
                        tempArgVisitor = new EngineVisitor();
                        exp.ArgumentList[i].Accept(tempArgVisitor);
                        tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ParameterList[i], tempArgVisitor.tempAns[0]);
                    }
                    for (int i = 0; i < ((FunctionDefinitionStatement)function).ReturnList.Length; i++)
                    {
                        tempVisitor.variableTable.AddVariable(((FunctionDefinitionStatement)function).ReturnList[i], null);
                    }
                    ((FunctionDefinitionStatement)function).FunctionBody.Accept(tempVisitor);
                    tempAns = tempVisitor.tempAns;
                    variableTable = tempVisitor.variableTable;
                    return;
                }
                if (calledFunction is BuiltInFunction)
                {
                    return;
                }
            }

        }
                  

        public void Visit(NDimArrayBuilder exp)
        {
            //TODO:netko
            EngineVisitor tempVisitor;
            QFloat64Array tempMatrix = new QFloat64Array(new int[] { exp.Exprs.Length, exp.Exprs[0].Length });
            for (int i=0; i<tempMatrix.DimensionSize[0]; i++)
                for (int j = 0; j < tempMatrix.DimensionSize[1]; j++)
                {
                    exp.Exprs[i][j].Accept(this);
                   // tempAns[i, j] = tempVisitor.TempAns[0, 0]; //e ovaj ndimarray je malo konfuzan.. u sustirni uzimam
                    /*
                     * NDimArray treba da moze da prosiruje matrice ako manjoj dodelimo vecu(valjda :S), pa ovo prolazi uvek,
                     * mada mislim da svi expresni treba da vrate matru 1x1...
                    */
                }
        }

        public void Visit(SubCell exp)
            // TODO: netko
        {
            throw new NotImplementedException();
        }

        public void Visit(AnonymousFunctionBuilder exp)
        {
            //TODO: netko
            throw new NotImplementedException();
        }

        public void Visit(CellBuilder exp)
        {
            //TODO: netko
            throw new NotImplementedException();
        }

        public void Visit(SingleExpressionStatement stmt)
        {
            //TODO: netko
            throw new NotImplementedException();
        }

        public void Visit(AssignmentStatement stmt)
        {
            //TODO: andrija
            Expression[] lhs = stmt.LeftHandSide;
            Expression rhs = stmt.RightHandSide;

            rhs.Accept(this);
            IEnumerator <IQLabValue> enumerator = tempAns.GetEnumerator(); // TODO: check position

            for (int i = 0; i < lhs.Length; i++)
            {
                Expression id = lhs[i];
                enumerator.MoveNext();
                IQLabValue rhValue = enumerator.Current;

                if (id is Identifier)
                    variableTable.AddVariable((Identifier)id, rhValue);
                else if (id is Call)
                {
                    // id is not an identifier, but an expression -> submatrices...!
                    Call callId = (Call)id;
                    if (callId.Called is Identifier) // TODO: if not?
                    {
                        Identifier name = (Identifier)callId.Called;

                        Expression[] arguments = callId.ArgumentList;
                        int[] coordinates = new int[arguments.Length]; // TODO: not ints?
                        for (int j = 0; j < arguments.Length; j++)
                        {
                            arguments[i].Accept(this); // TODO: not this?
                            if (this.tempAns[0] is QInt32) // TODO: not correct type?
                                coordinates[i] = ((QInt32)this.tempAns[0]).Real; 
                        }

                        variableTable.ChangeArrayVariable(name, coordinates, rhValue);
                    }

                }
                else // TODO: check if there are other possibilities
                {
                    throw new NotImplementedException();
                }
            }
        }

        public void Visit(BlockStatement stmt)
        {
            //TODO: andrija, PAZITI NA BREAK I CONTINUE i vracanja iz break i continue
            throw new NotImplementedException();
        }

        public void Visit(IfThenElseStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(SwitchCaseStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(ForStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(WhileStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(ContinueStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(BreakStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(TryCatchStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(ReturnStatement stmt)
        {
            //TODO: mislite o tome :D, zaustavlja obilazak
            throw new NotImplementedException();
        }

        public void Visit(FunctionDefinitionStatement stmt)
        {
            AddNestedFunctions(stmt);
            for (int i = 0; i < stmt.ReturnList.Length; i++)
            {
                variableTable.AddVariable(stmt.ReturnList[i], null);
            }
        }

        public void Visit(GlobalDeclarationStatement stmt)
        {
            throw new NotImplementedException();
        }

        public void Visit(PersistentDeclarationStatement stmt)
        {
            throw new NotImplementedException();
        }
    }
}
